
#include "Common.hpp"
using namespace std;

namespace {

//-----------------------------------------------------------------------------
const char* nVertexProgramSrc =
    "!!NVvp4.0"
    NL "FLOAT ATTRIB iPosition = vertex.attrib[0];"
    NL "FLOAT ATTRIB iColor = vertex.attrib[1];"
    NL "FLOAT PARAM ModelviewProj[4] = { program.local[0..3] };"
    NL "FLOAT OUTPUT oPosition = result.position;"
    NL "FLOAT OUTPUT oColor = result.attrib[0];"
    NL
    NL "main:"
    NL "    DP4.F oPosition.x, ModelviewProj[0], iPosition;"
    NL "    DP4.F oPosition.y, ModelviewProj[1], iPosition;"
    NL "    DP4.F oPosition.z, ModelviewProj[2], iPosition;"
    NL "    DP4.F oPosition.w, ModelviewProj[3], iPosition;"
    NL
    NL "    MOV oColor, iColor;"
    NL "    RET;"
    NL "END";

const char* nFragmentProgramSrc =
    "!!NVfp4.0"
    NL "FLOAT ATTRIB iColor = fragment.attrib[0];"
    NL "FLOAT OUTPUT oColor = result.color;"
    NL
    NL "main:"
    NL "    MOV oColor, iColor;"
    NL "    RET;"
    NL "END";
//-----------------------------------------------------------------------------
Window* nWindow;
SDL_GLContext nContext;

GLuint nProgram[2];
GLuint nVBO;
GLuint64 nVBOAddress;

ArcBall nArcBall;
//-----------------------------------------------------------------------------
void Setup() {
    // window
    nWindow = new Window("Triangle", 800, 600);

    // OpenGL context
    nContext = SDL_GL_CreateContext(nWindow->Get());
    if (!nContext) {
        throw runtime_error("[Setup] SDL_GL_CreateContext failed.");
    }

    SDL_GL_SetSwapInterval(0);

    if (glewInit() != GLEW_OK) {
        throw runtime_error("[Setup] glewInit failed.");
    }
    if (!GLEW_NV_gpu_program4) {
        throw runtime_error("[Setup] GL_NV_gpu_program4 extension is not supported.");
    }

    // prograa
    glGenProgramsARB(2, nProgram);

    glBindProgramARB(GL_VERTEX_PROGRAM_ARB, nProgram[0]);
    LoadGLProgram(GL_VERTEX_PROGRAM_ARB, nVertexProgramSrc);
    cout << GetGLProgramInfo(GL_VERTEX_PROGRAM_ARB);

    // fragment program
    glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, nProgram[1]);
    LoadGLProgram(GL_FRAGMENT_PROGRAM_ARB, nFragmentProgramSrc);
    cout << GetGLProgramInfo(GL_FRAGMENT_PROGRAM_ARB);

    // vbo
    const float s = 1.0f;
    const float vertices[] = {
        -s, -s, 1.0f, 0.0f, 0.0f,
        s, -s, 0.0f, 1.0f, 0.0f,
        0.0f, s, 0.0f, 0.0f, 1.0f
    };

    glGenBuffers(1, &nVBO);
    glBindBuffer(GL_ARRAY_BUFFER, nVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glGetBufferParameterui64vNV(GL_ARRAY_BUFFER, GL_BUFFER_GPU_ADDRESS_NV, &nVBOAddress);
    glMakeBufferResidentNV(GL_ARRAY_BUFFER, GL_READ_ONLY);

    // GL state
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_VERTEX_PROGRAM_ARB);
    glEnable(GL_FRAGMENT_PROGRAM_ARB);

    // init GUI
    TwInit(TW_OPENGL, NULL);
    TwWindowSize(nWindow->Width(), nWindow->Height());

    TwBar* bar = TwNewBar("Usage");
    TwDefine("Usage color='0 0 0' alpha=128 position='10 10' size='240 50'");
    TwDefine("Usage fontresizable=false resizable=false movable=false");
    TwAddButton(bar, "usage0", NULL, NULL, "label='- mouse with LB down to rotate'");

    nArcBall.SetWindowSize(nWindow->Width(), nWindow->Height());
}
//-----------------------------------------------------------------------------
bool ProcessEvents() {
    SDL_Event evt;
    while (SDL_PollEvent(&evt)) {
        if (evt.type == SDL_QUIT) {
            return false;
        }

        if (ProcessCommonEvent(evt) || ProcessGUIEvent(evt)) {
            continue;
        }

        switch (evt.type) {
        case SDL_MOUSEBUTTONDOWN:
            if (evt.button.button == SDL_BUTTON_LEFT) {
                nArcBall.Begin(evt.button.x, evt.button.y);
                return true;
            }
            break;

        case SDL_MOUSEBUTTONUP:
            if (evt.button.button == SDL_BUTTON_LEFT) {
                nArcBall.End();
                return true;
            }
            break;

        case SDL_MOUSEMOTION:
            nArcBall.Drag(evt.motion.x, evt.motion.y);
            break;
        }
    }

    return true;
}
//-----------------------------------------------------------------------------
void Update() {
    // clear buffers
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // compute transform matrices
    const glm::mat4 m = nArcBall.Rotation();
    const glm::mat4 mv = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f)) * m;
    const glm::mat4 mvp = glm::transpose(glm::perspective(45.0f, 1.333f, 0.1f, 10.0f) * mv);

    // draw
    glBindProgramARB(GL_VERTEX_PROGRAM_ARB, nProgram[0]);
    glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, nProgram[1]);

    glProgramLocalParameters4fvEXT(GL_VERTEX_PROGRAM_ARB, 0, 4, &mvp[0][0]);

    glEnableClientState(GL_VERTEX_ATTRIB_ARRAY_UNIFIED_NV);
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    glVertexAttribFormatNV(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float));
    glVertexAttribFormatNV(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float));

    const GLsizeiptr size = 3 * 5 * sizeof(float);
    glBufferAddressRangeNV(GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV, 0, nVBOAddress, size);
    glBufferAddressRangeNV(GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV, 1, nVBOAddress + sizeof(float) * 2, size - sizeof(float) * 2);

    glDrawArrays(GL_TRIANGLES, 0, 3);

    // draw GUI
    glDisableClientState(GL_VERTEX_ATTRIB_ARRAY_UNIFIED_NV);
    glDisableClientState(GL_ELEMENT_ARRAY_UNIFIED_NV);
    for (GLuint i = 0; i < 16; ++i) {
        glDisableVertexAttribArray(i);
    }
    TwDraw();

    nWindow->Swap();
}
//-----------------------------------------------------------------------------
void Run() {
    Setup();

    while (ProcessEvents()) {
        Update();
    }
}
//-----------------------------------------------------------------------------
void Cleanup() {
    glDeleteBuffers(1, &nVBO);
    glDeleteProgramsARB(2, nProgram);

    TwTerminate();

    SDL_GL_DeleteContext(nContext);
    delete nWindow;
}
//-----------------------------------------------------------------------------

} // namespace

//-----------------------------------------------------------------------------
int main(int argc, char** argv) {
    if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO) < 0) {
        cout << "[main] SDL_Init failed." << endl;
        return 0;
    }

    try {
        Run();
    }
    catch (const exception& ex) {
        cout << ex.what() << endl;
    }
    catch (...) {
        cout << "Undefined exception." << endl;
    }

    Cleanup();
    SDL_Quit();

    return 0;
}
//-----------------------------------------------------------------------------
